from common.logget import logger
import json
import os
import jsonpath
#读取yaml文件的-eq或者多个断言 进行循环断言
from common.read_yaml import get_yaml_data,write_yaml
from common.db import DB
api_path=os.path.dirname(os.path.dirname(__file__))#获取项目路径 C:/Users/Administrator/.PyCharmCE2019.2/py_test
case_path=os.path.join(api_path,'case') #测试脚本所在路径 C:/Users/Administrator/.PyCharmCE2019.2/py_test\case
data_path=os.path.join(api_path,'data') #测试数据所在路径 C:/Users/Administrator/.PyCharmCE2019.2/py_test\data
extract =data_path + '/extract.yaml'  #提取的关键字数据所在路径


#jsonpath 断言
def assert_response(response, validate):
    '''设置断言'''
    #validate等于[{'eq': ['$.code', 200]}, {'eq': ['$.message', '请求成功']}]
    try:
        for i in validate:
            if "eq" in i.keys():
                yaml_result = i.get("eq")[0]
                #actual_result = jsonpath.jsonpath(response.json(),f'$..{yaml_result}')#去掉$..是考虑返回的json数据如果存在两个一样的id
                actual_result = jsonpath.jsonpath(response.json(), f'{yaml_result}')#jsonpath提取的值  取到的值在列表里 如 [200]
                expect_result = i.get("eq")[1] #yaml文件中 写的期望值
                # print(f"实际结果：{actual_result[0]}")
                # print(f"期望结果：{expect_result}")
                try:
                    assert actual_result[0] == expect_result
                    #这里不能写return 不然只会断言1个数据  就不在循环
                except AssertionError:
                     # logger.exception(f"实际结果【{actual_result[0]}】 不等于 期望值【{expect_result}】") 这里省略了 下面会提示
                     raise AssertionError(f"实际结果【{actual_result[0]}】 不等于 期望值【{expect_result}】")  # 在使用Python3做自动化测试过程中可能会遇到,assert函数不加try  except，就可以正常在报告里体现用例不通过，
                                                                                                             # 加上变成通过。这是因为在使用try except 时,捕获了assert函数产生的AssertionError异常,导致异常没有上抛,这时只需要在后面加上 raise 就可以再次把它抛出。
            elif "contain" in i.keys():
                result = response.text  # jsonpath提取的文本
                expect = i.get("contain")  # yaml文件中 写的期望值

                try:
                    assert expect in result   #文本包含断言
                except AssertionError:
                     raise AssertionError(f"实际结果【{result}】 不等于 期望值【{expect}】")  #

            elif "regex_match" in i.keys():#正则表达式
                pass

            elif "db" in i.keys(): #数据库
                sql=i.get("eq")[0]
                d=DB() #实例化
                le=d.query(sql)
                assert len(le) == 1
    except Exception as e: #如果是断言前面参数写错了 会输出扑捉到e的信息，如果断言失败 则e 就是上面AssertionError的信息
        logger.exception(f"jsonpath (断言) 失败，请检查断言信息 数据是否正确：{validate}异常信息为：{e}")
        raise AssertionError(f"jsonpath (断言) 失败，请检查断言信息 数据是否正确：\n{validate}\n异常信息为：{e}")  #raise不抛异常的话  会默认执行成功  allure报告只认AssertionError异常


#接口关联提取方法
# def get_text(res,key):
#     # 将字符串作为输入并返回字典作为输出。
#     # text = json.loads(res)
#     #将字符串作为输入并返回字典作为输出。
#     # text1 = json.dumps(res)
#     try:
#         ty_pe = type(res)
#         if ty_pe is str:
#             text = json.loads(res)
#         else:
#             text =res
#         #jsonpath就相当于ui自动化中的xpath元素定位
#         #value = jsonpath.jsonpath(text,f'$..{key}')
#         #练习请查看jsonpath_tes.py文件
#         value = jsonpath.jsonpath(text, f'{key}') #text等于响应过后的json数据 如 re.json
#         if value:
#             if len(value) == 1:
#                 return value[0]
#
#         else:
#             return None
#     except Exception:
#         raise Exception("接口关联提取失败")




#获取响应参数(可以提取多个根据yaml文件的 response_extraction来判断)，自定义键然后写入文件/data/extract.yaml 文件
def get_resp(response, extract_key):
    """
    :param response:   请求的响应数据
    :param extract_key:  需要写入yaml文件的自定义关键字加值  key：value
    :return:
    """

    if extract_key:
        try:
            for i in extract_key:
                if "rex" in i.keys():
                    yaml_result = i.get("rex")[0]    #jsonpath定位方法
                    # actual_result = jsonpath.jsonpath(response.json(),f'$..{yaml_result}')#去掉$..是考虑返回的json数据如果存在两个一样的id
                    extract_value = jsonpath.jsonpath(response.json(), f'{yaml_result}') #jsonpath提取的值  取到的值返回在列表里 如 [200]

                    input_key = i.get("rex")[1]  #自定义的键
                    # print(input_key)
                    new_dict = {input_key: extract_value[0]}
                    write_yaml(new_dict) #写入yaml文件
                    print(f"提取响应数据，并自定义关键字{new_dict}成功写入extract.yaml")
        except Exception as e:
            logger.error(f"jsonpath （响应数据）提取失败__请检查{extract_key}写法是否正确。异常信息为：{e}")
            raise AssertionError(f"jsonpath （响应数据）提取失败__请检查{extract_key}写法是否正确。异常信息为：{e}") #抛出异常后  程序终止 后面的程序不在执行
            # print(f"jsonpath提取器提取失败请检查写法是否正确。异常信息为：{e}")
    else:
        print("此请求不需要提取响应参数")






#此函数作用是提取/data/extract.yaml 文件的指定关键字，再发送请求前添加至请求体 做接口的关联
def change_data(case):
      """

      :param case: 提取关键字数据。更新在入参的字典中  或 拼接在url后面（delete）
      :return:
      """
      apiname = case['title']
      #case={'json': {'pageNum': 1, 'pageSize': 20},'response_extraction': [{'rex': ['$.id', 'yqid']}], 'relation': {'id': 'yqid', 'na': 'ida'}, 'expected': [{'eq': ['$.code', 200]}, {'eq': ['$.message', '请求成功']}]}
      try:
          method = case['request']['method']
          relation = case['relation']#入参的键需要参考文档 提前写好
          data = case['json']
          url = case['request']['url']

          # 更新鉴权码 token
          header = case['headers']
          extract = get_yaml_data(data_path + '/extract.yaml')
          token = {'Authorization': extract['Authorization']} #login_token提取时 存入的变量为Authorization
          # 以字典的形式 直接更新在headers里面
          header.update(token)
          if relation: #如果不为空,则需要提取响应数据 做关联
              value=get_yaml_data(data_path + '/extract.yaml')   #error 写../data/extract.yaml路径在跑pytest框架会出错 会在读取文件后返回None

              for i in relation:
                  if method.lower() == 'delete':
                      url = url+value[relation[i]]     #在Python中,字符串属于不可变对象,不支持原地修改,如果需要修改其中的值,只能重新创建一个新的字符串对象
                      return url
                  else:
                      j = {i: value[relation[i]]}  # i 为系统入参需要的键 id， relation[i] 为获取自定义关键字的键 是yqid
                      data.update(j)  # post、get请求 则 更新请求体数据


          else:     #如果用例response_extraction字段为空，则此请求不需要提取响应做关联
              print("此请求不需要提取extract.yaml文件中的关联数据")
      except Exception as e:
          logger.error(f"{apiname}接口关键字提取失败...请检查extract.yaml文件中是否存在此关键字")







